/*
 * Copyright 2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as _ from "lodash"
import { camelCase, snakeCase } from "../case"
import { OptionSelection } from "../catalog"
import { PageList } from "../data"
import httpClient from "../httpclient"
import { Payment } from "../payment"
import { Address } from "../shipping"
import { OrderDiscount } from "./discount"
import { OrderPayment } from "./payment"
import { OrderRating } from "./rating"
import { assignOrderRefund, OrderRefund } from "./refund"
import { OrderReview } from "./review"

export * from "./discount"
export * from "./dispute"
export * from "./payment"
export * from "./rating"
export * from "./refund"
export * from "./review"

export class ShipmentItem {
  public id: string
  public productId: string
  public variantId: string
  public quantity: string
  public name: string
  public imageUrl: string
}

export class Shipment {
  public id: string
  public consignor: string
  private shipping_provider: string
  private tracking_carrier: string
  private shipping_method: string
  private tracking_number: string
  private order_id: string
  public items: ShipmentItem[] = []
  private shipped_time: string
  private image_urls: string

  public containsItem(item: OrderItem): boolean {
    return _.find(this.items, aItem => aItem.productId === item.productId && aItem.variantId === item.variantId) !== undefined
  }

  public get shippingProvider() {
    return this.shipping_provider
  }

  public set shippingProvider(shippingProvider) {
    this.shipping_provider = shippingProvider
  }

  public get trackingCarrier() {
    return this.tracking_carrier
  }

  public set trackingCarrier(trackingCarrier) {
    this.tracking_carrier = trackingCarrier
  }

  public get shippingMethod() {
    return this.shipping_method
  }

  public set shippingMethod(shippingMethod) {
    this.shipping_method = shippingMethod
  }

  public get trackingNumber() {
    return this.tracking_number
  }

  public set trackingNumber(trackingNumber) {
    this.tracking_number = trackingNumber
  }

  public get orderId() {
    return this.order_id
  }

  public set orderId(orderId) {
    this.order_id = orderId
  }

  public get shippedTime() {
    return this.shipped_time
  }

  public set shippedTime(shippedTime) {
    this.shipped_time = shippedTime
  }

  public get imageUrls() {
    return this.image_urls
  }
}

export class ShipmentRequest {
  public id: string
  public shipping_provider?: string
  public shipping_method?: string
  public tracking_number?: string

  public set shippingProvider(shippingProvider: string) {
    this.shipping_provider = shippingProvider
  }

  public set shippingMethod(shippingMethod: string) {
    this.shipping_method = shippingMethod
  }

  public set trackingNumber(trackingNumber: string) {
    this.tracking_number = trackingNumber
  }
}

function assignOrderShipment(shipment: Shipment) {
  return _.assign(new Shipment(), shipment, {
    items: _.map(shipment.items, item => _.assign(new OrderItem(), item)),
  })
}

export enum OrderSource {
  Browser = "browser", Other = "other"
}

export enum OrderStatus {
  Incomplete = "incomplete",
  Pending = "pending",
  AwaitingPayment = "awaiting_payment",
  AwaitingFulfillment = "awaiting_fulfillment",
  AwaitingShipment = "awaiting_shipment",
  PartiallyShipped = "partially_shipped",
  Shipped = "shipped",
  //
  AwaitingRefund = "awaiting_refund",
  PartiallyRefunded = "partially_refunded",
  Refunded = "refunded",
  //
  AwaitingReview = "awaiting_review",
  PartiallyReviewed = "partially_reviewed",
  Reviewed = "reviewed",
  //
  AwaitingPickup = "awaiting_pickup",
  Cancelled = "cancelled",
  Closed = "closed",
  Declined = "declined",
  Completed = "completed",
  Disputed = "disputed",
}

export class OrderItem {
  public id: string
  public storeId: string
  public productId: string
  public variantId: string
  public quantity: number
  public price: number
  public amount: number
  public name: string
  public optionSelections: OptionSelection[] = []
  public imageUrl: string
  public refundedAmount: number
  public refundingAmount: number
  public shippingCost: number
  public discountShippingCost: number
  public discountTotalPrice: number
  public totalPrice: number
  public subtotalAmount: number
  public totalAmount: number
  public shipped: boolean
}

export class Order {
  public id: string
  public status: OrderStatus
  public reviewStatus: OrderStatus
  public staffNotes: string
  public staffStars: number
  public items: OrderItem[] = []
  public itemsSize: number
  public storeId: string
  public storeName: string
  public customerId: string
  public customerMessage: string
  public source: OrderSource
  public shippingAddress: Address = new Address()
  public shipments: Shipment[] = []
  public refunds: OrderRefund[]
  public placingExpires: number = 0
  public placingExpiredTime: string
  public paymentId ?: string
  public paymentStatus ?: string
  public paymentMethod ?: string
  public cancelReason ?: string
  public closeReason ?: string
  public declineReason ?: string
  public totalPrice: number
  public totalQuantity: number
  public subtotalAmount: number
  public totalAmount: number
  public totalShippingCost: number
  public totalDiscountTotalPrice: number
  public totalDiscountShippingCost: number

  public placedTime: string
  public paidTime: string
  public shippedTime: string
  public receivedTime: string
  public cancelledTime: string
  public closedTime: string

  public getItem(itemId: string): OrderItem {
    return _.find(this.items, item => item.id === itemId) as OrderItem
  }
}

export function assignOrder(order: Order) {
  return _.assign(new Order(), order, {
    shippingAddress: _.assign(new Address(), order.shippingAddress),
    items: _.map(order.items, item => _.assign(new OrderItem(), item)),
    shipments: _.map(order.shipments, assignOrderShipment),
    refunds: _.map(order.refunds, assignOrderRefund),
  })
}

export class OrderQuery {

  private params = {}

  public page(page?: string | number): OrderQuery {
    if (page) {
      this.params["page"] = page
    }
    return this
  }

  public limit(limit?: string | number): OrderQuery {
    if (limit) {
      this.params["limit"] = limit
    }
    return this
  }

  public ids(ids?: string | string[]): OrderQuery {
    if (ids) {
      this.params["ids"] = ids
    }
    return this
  }

  public customerId(customerId: string): OrderQuery {
    this.params["customer_id"] = customerId
    return this
  }

  public statuses(statuses?: OrderStatus[] | string): OrderQuery {
    if (statuses) {
      this.params["statuses"] = statuses
    }
    return this
  }

  public refundStatuses(statuses?: OrderStatus[] | string): OrderQuery {
    if (statuses) {
      this.params["refund_statuses"] = statuses
    }
    return this
  }

  public reviewStatuses(statuses?: OrderStatus[] | string): OrderQuery {
    if (statuses) {
      this.params["review_statuses"] = statuses
    }
    return this
  }


  public placedTimeMin(placedTimeMin: string): OrderQuery {
    if (placedTimeMin) {
      this.params["placed_time_min"] = placedTimeMin
    }
    return this
  }

  public placedTimeMax(placedTimeMax: string): OrderQuery {
    if (placedTimeMax) {
      this.params["placed_time_max"] = placedTimeMax
    }
    return this
  }

  public storeId(storeId: string): OrderQuery {
    this.params["store_id"] = storeId
    return this
  }

  public list(): Promise<PageList<Order>> {
    return httpClient.get("/v1/orders", {
      params: this.params,
    })
      .then(({ data }) => data)
      .then(camelCase)
      .then(data => {
        return _.assign(new PageList<Order>(), data, {
          elements: _.map(data.elements, assignOrder),
        })
      })
  }

  public count(): Promise<number> {
    return httpClient.get("/v1/orders/count", {
      params: this.params,
    }).then(({ data }) => data)
  }
}

export class OrderServiceV1 {

  public static createOrders(orders: Order[]): Promise<Order[]> {
    return httpClient.post("/v1/orders/batch", snakeCase(orders))
      .then(({ data }) => data)
      .then(camelCase)
      .then(({ data }) => _.map(data, assignOrder))
  }

  public static updateOrder(order: Order): Promise<void> {
    return httpClient.patch(`/v1/orders/${order.id}`, snakeCase(order))
  }

  public static discountOrder(orderId: string, discounts: OrderDiscount[]): Promise<Order> {
    return httpClient.patch(`/v1/orders/${orderId}/discounts/batch`, snakeCase(discounts))
      .then(({ data }) => data)
      .then(camelCase)
      .then(assignOrder)
  }

  public static cancelOrder(orderId: string, reason: string): Promise<void> {
    return httpClient.post(`/v1/orders/${orderId}/cancel`, {
      cancel_reason: reason,
    })
  }

  public static declineOrder(orderId: string, reason: string): Promise<void> {
    return httpClient.post(`/v1/orders/${orderId}/decline`, {
      decline_reason: reason,
    })
  }

  public static receiptOrder(orderId: string): Promise<void> {
    return httpClient.post(`/v1/orders/${orderId}/receipt`)
  }

  public static getOrder(orderId: string): Promise<Order> {
    return httpClient.get(`/v1/orders/${orderId}`)
      .then(({ data }) => data)
      .then(camelCase)
      .then(assignOrder)
  }

  // 包含 list 和 count
  public static createOrderQuery() {
    return new OrderQuery()
  }

  public static getOrderShipments(orderId: string): Promise<Shipment[]> {
    return httpClient.get(`/v1/orders/${orderId}/shipments`)
      .then(({ data: shipments }) => _.map(shipments, assignOrderShipment))
  }

  public static addOrderShipment(orderId: string, shipment: Shipment): Promise<Shipment> {
    return httpClient.post(`/v1/orders/${orderId}/shipments`, snakeCase(shipment))
      .then<Shipment>(({ data }) => data)
      .then(camelCase)
      .then(data => _.assign(new Shipment(), data))
  }

  public static updateOrderShipments(orderId: string, requests: ShipmentRequest[]): Promise<void> {
    return httpClient.patch(`/v1/orders/${orderId}/shipments/batch`, requests)
  }

  public static startOrderPayment(orderPayment: OrderPayment): Promise<Payment> {
    return httpClient.post(`/v1/orders/payments`, orderPayment)
      .then(({ data }) => _.assign(new Payment(), data))
  }

  public static applyOrderRefund(orderId: string, refund: OrderRefund): Promise<OrderRefund> {
    return httpClient.post(`/v1/orders/${orderId}/refunds`, refund)
      .then(({ data }) => data)
      .then(assignOrderRefund)
  }

  public static applyOrderRefunds(orderId: string, refunds: OrderRefund[]): Promise<OrderRefund[]> {
    return httpClient.post(`/v1/orders/${orderId}/refunds/batch`, refunds)
      .then(({ data }) => _.map(data, assignOrderRefund))
  }

  public static cancelOrderRefund(orderId: string, refundId: string): Promise<void> {
    return httpClient.delete(`/v1/orders/${orderId}/refunds/${refundId}/cancel`)
  }

  public static disapproveOrderRefund(refund: OrderRefund): Promise<void> {
    const { orderId, id } = refund
    return httpClient.patch(`/v1/orders/${orderId}/refunds/${id}/disapprove`, refund)
  }

  public static approveOrderRefund(orderId: string, refundId: string): Promise<void> {
    return httpClient.patch(`/v1/orders/${orderId}/refunds/${refundId}/approve`)
  }

  public static getOrderRefund(orderId: string, refundId: string): Promise<OrderRefund> {
    return httpClient.get(`/v1/orders/${orderId}/refunds/${refundId}`)
      .then(({ data }) => data)
      .then(assignOrderRefund)
  }

  public static reviewOrder(orderId: string, reviews: OrderReview[]): Promise<OrderReview[]> {
    return httpClient.post(`/v1/orders/${orderId}/reviews/batch`, snakeCase(reviews))
      .then(({ data }) => data)
      .then(camelCase)
      .then(aReviews => _.map(aReviews, aReview => _.assign(new OrderReview(), aReview)))
  }

  public static ratingOrder(orderId: string, ratings: OrderRating[]): Promise<void> {
    return httpClient.post(`/v1/orders/${orderId}/ratings/batch`, snakeCase(ratings))
      .then(({ data }) => data)
  }
}
